/**
 * Copyright (c) 2008, Mu Dynamics.
 *  All rights reserved.
 *  
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *   
 *  - Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation and/or 
 *     other materials provided with the distribution.
 *  - Neither the name of the "Mu Dynamics" nor the names of its contributors may be used 
 *     to endorse  or promote products derived from this software without specific prior 
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.mu.jacob.core.model.decorator;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;

import org.apache.log4j.Logger;

import com.mu.jacob.core.generator.Decorate;
import com.mu.jacob.core.generator.GeneratorException;

/**
 * Element decorator
 * @author Adam Smyczek
 *
 * @param <T> to decorate element or extended type
 */
public class DefaultElement<T extends Element> extends DefaultDecorator<T> implements Element  {

	/** The decorated element */
	private T element;

	/**
	 * @see com.mu.jacob.core.model.decorator.DefaultDecorator#getDecorated()
	 */
	public T getDecorated() {
		return element;
	}

	/**
	 * @see com.mu.jacob.core.model.decorator.DefaultDecorator#getApiClass()
	 */
	@SuppressWarnings("unchecked")
	public Class<T> getApiClass() {
		return (Class<T>)Element.class;
	}

	/**
	 * @see com.mu.jacob.core.model.decorator.DefaultDecorator#initialize(java.lang.Object)
	 */
	public void initialize(T model) {
		this.element = model;
	}

	/**
	 * @see javax.lang.model.element.Element#accept(javax.lang.model.element.ElementVisitor, java.lang.Object)
	 */
	@Decorate
	public <R, P> R accept(ElementVisitor<R, P> v, P p) {
		return getDecorated().accept(v, p);
	}

	/**
	 * @see javax.lang.model.element.Element#asType()
	 */
	@Decorate
	public TypeMirror asType() {
		return getDecorated().asType();
	}

	/**
	 * Type accessor, same as asType().
	 * This accessor allows to use <code>element.type</code> notation in template files
	 * @return returns type of the element
	 */
	public TypeMirror getType() {
		return asType();
	}
	
	/**
	 * @see javax.lang.model.element.Element#getAnnotation(java.lang.Class)
	 */
	@Decorate
	public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
		return getDecorated().getAnnotation(annotationType);
	}

	/**
	 * @see javax.lang.model.element.Element#getAnnotationMirrors()
	 */
	@Decorate
	public List<? extends AnnotationMirror> getAnnotationMirrors() {
		return getDecorated().getAnnotationMirrors();
	}

	/**
	 * Helper method
	 * @return annotation mirror for a annotation qualified name
	 */
	public AnnotationMirror getAnnotationMirror(String annotationQualifiedName) {
		if (annotationQualifiedName != null) {
			for (AnnotationMirror annotationMirror : getAnnotationMirrors()) {
				if (annotationQualifiedName.equals(annotationMirror.getAnnotationType().asElement().toString())) {
					return annotationMirror;
				}
			}
		}
		return null;
	}
	
	/**
	 * @see javax.lang.model.element.Element#getEnclosedElements()
	 */
	@Decorate
	public List<? extends Element> getEnclosedElements() {
		return getDecorated().getEnclosedElements();
	}

	/**
	 * @see javax.lang.model.element.Element#getEnclosingElement()
	 */
	@Decorate
	public Element getEnclosingElement() {
		return getDecorated().getEnclosingElement();
	}

	/**
	 * @see javax.lang.model.element.Element#getKind()
	 */
	public ElementKind getKind() {
		return getDecorated().getKind();
	}

	/**
	 * @see javax.lang.model.element.Element#getSimpleName()
	 */
	public Name getSimpleName() {
		return getDecorated().getSimpleName();
	}

	/**
	 * @see javax.lang.model.element.Element#getModifiers()
	 */
	public Set<Modifier> getModifiers() {
		return getDecorated().getModifiers();
	}

	/**
	 * Modifier helper method  
	 * @param modifierString the string representation of the modifier e.g. <code>abstract</code>
	 * @return returns true if modifier list contains a modifier for modifierString
	 */
	public boolean is(String modifierString) {
		try {
			Modifier modifier = Modifier.valueOf(modifierString.toUpperCase());
			return getModifiers().contains(modifier);
		} catch (RuntimeException e) {
			logger.error("Invalid modifier string " + modifierString);
			return false;
		}
	}

	/**
	 * Generates modifier string
	 * @return returns concatenated modifier string e.g. <code>public abstract</code>
	 */
	public String getModifiersString() {
		StringBuffer buffer = new StringBuffer();
		for (Modifier modifier : getModifiers()) {
			buffer.append(modifier).append(" ");
		}
		return buffer.toString().trim();
	}
	
	/**
	 * Filters enclosed elements on <code>elementKind</code>
	 * @param elementKind string representation of the element kind e.g. class, interface
	 * @return returns set of enclosed elements of kind <code>elementKind</code>
	 */
	public Set<? extends Element> getEnclosedElements(String elementKind) {
		try {
			String upperCase = elementKind.toUpperCase();
			return Filter.filterElements(getEnclosedElements(), ElementKind.valueOf(upperCase));
		} catch (Exception e) {
			throw new GeneratorException("Invalid element kind " + elementKind, e);
		}
	}

	private static Logger logger = Logger.getLogger(DefaultElement.class);

}
